
/* 
 * Copyright (c) 2006-2012 by Roland Riegel <feedback@roland-riegel.de>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

#include "byteordering.h"
#include "partition.h"
#include "fat.h"
#include "fat_config.h"
#include "sd-reader_config.h"
#include "inttypes.h"

#include <string.h>

#if USE_DYNAMIC_MEMORY
#include <stdlib.h>
#endif

/**
 * \addtogroup fat FAT support
 *
 * This module implements FAT16/FAT32 read and write access.
 * 
 * The following features are supported:
 * - File names up to 31 characters long.
 * - Unlimited depth of subdirectories.
 * - Short 8.3 and long filenames.
 * - Creating and deleting files.
 * - Reading and writing from and to files.
 * - File resizing.
 * - File sizes of up to 4 gigabytes.
 * 
 * @{
 */
/**
 * \file
 * FAT implementation (license: GPLv2 or LGPLv2.1)
 *
 * \author Roland Riegel
 */

/**
 * \addtogroup fat_config FAT configuration
 * Preprocessor defines to configure the FAT implementation.
 */

/**
 * \addtogroup fat_fs FAT access
 * Basic functions for handling a FAT filesystem.
 */

/**
 * \addtogroup fat_file FAT file functions
 * Functions for managing files.
 */

/**
 * \addtogroup fat_dir FAT directory functions
 * Functions for managing directories.
 */

/**
 * @}
 */

#define FAT16_CLUSTER_FREE 0x0000
#define FAT16_CLUSTER_RESERVED_MIN 0xfff0
#define FAT16_CLUSTER_RESERVED_MAX 0xfff6
#define FAT16_CLUSTER_BAD 0xfff7
#define FAT16_CLUSTER_LAST_MIN 0xfff8
#define FAT16_CLUSTER_LAST_MAX 0xffff

#define FAT32_CLUSTER_FREE 0x00000000
#define FAT32_CLUSTER_RESERVED_MIN 0x0ffffff0
#define FAT32_CLUSTER_RESERVED_MAX 0x0ffffff6
#define FAT32_CLUSTER_BAD 0x0ffffff7
#define FAT32_CLUSTER_LAST_MIN 0x0ffffff8
#define FAT32_CLUSTER_LAST_MAX 0x0fffffff

#define FAT_DIRENTRY_DELETED 0xe5
#define FAT_DIRENTRY_LFNLAST (1 << 6)
#define FAT_DIRENTRY_LFNSEQMASK ((1 << 6) - 1)

/* Each entry within the directory table has a size of 32 bytes
 * and either contains a 8.3 DOS-style file name or a part of a
 * long file name, which may consist of several directory table
 * entries at once.
 *
 * multi-byte integer values are stored little-endian!
 *
 * 8.3 file name entry:
 * ====================
 * offset  length  description
 *      0       8  name (space padded)
 *      8       3  extension (space padded)
 *     11       1  attributes (FAT_ATTRIB_*)
 *
 * long file name (lfn) entry ordering for a single file name:
 * ===========================================================
 * LFN entry n
 *     ...
 * LFN entry 2
 * LFN entry 1
 * 8.3 entry (see above)
 * 
 * lfn entry:
 * ==========
 * offset  length  description
 *      0       1  ordinal field
 *      1       2  unicode character 1
 *      3       3  unicode character 2
 *      5       3  unicode character 3
 *      7       3  unicode character 4
 *      9       3  unicode character 5
 *     11       1  attribute (always 0x0f)
 *     12       1  type (reserved, always 0)
 *     13       1  checksum
 *     14       2  unicode character 6
 *     16       2  unicode character 7
 *     18       2  unicode character 8
 *     20       2  unicode character 9
 *     22       2  unicode character 10
 *     24       2  unicode character 11
 *     26       2  cluster (unused, always 0)
 *     28       2  unicode character 12
 *     30       2  unicode character 13
 * 
 * The ordinal field contains a descending number, from n to 1.
 * For the n'th lfn entry the ordinal field is or'ed with 0x40.
 * For deleted lfn entries, the ordinal field is set to 0xe5.
 */

struct fat_header_struct {
	offset_t size;

	offset_t fat_offset;
	uint32 fat_size;

	uint16 sector_size;
	uint16 cluster_size;

	offset_t cluster_zero_offset;

	offset_t root_dir_offset;
#if FAT_FAT32_SUPPORT
	cluster_t root_dir_cluster;
#endif
};

struct fat_fs_struct {
	struct partition_struct *partition;
	struct fat_header_struct header;
	cluster_t cluster_free;
};

struct fat_file_struct {
	struct fat_fs_struct *fs;
	struct fat_dir_entry_struct dir_entry;
	offset_t pos;
	cluster_t pos_cluster;
};

struct fat_dir_struct {
	struct fat_fs_struct *fs;
	struct fat_dir_entry_struct dir_entry;
	cluster_t entry_cluster;
	uint16 entry_offset;
};

struct fat_read_dir_callback_arg {
	struct fat_dir_entry_struct *dir_entry;
	uintptr_t bytes_read;
#if FAT_LFN_SUPPORT
	uint8 checksum;
#endif
	uint8 finished;
};

struct fat_usage_count_callback_arg {
	cluster_t cluster_count;
	uintptr_t buffer_size;
};

#if !USE_DYNAMIC_MEMORY
static struct fat_fs_struct fat_fs_handles[FAT_FS_COUNT];
static struct fat_file_struct fat_file_handles[FAT_FILE_COUNT];
static struct fat_dir_struct fat_dir_handles[FAT_DIR_COUNT];
#endif

static uint8 fat_read_header(struct fat_fs_struct *fs);
static cluster_t fat_get_next_cluster(const struct fat_fs_struct *fs,
				      cluster_t cluster_num);
static offset_t fat_cluster_offset(const struct fat_fs_struct *fs,
				   cluster_t cluster_num);
static uint8 fat_dir_entry_read_callback(uint8 * buffer, offset_t offset,
					 void *p);
#if FAT_LFN_SUPPORT
static uint8 fat_calc_83_checksum(const uint8 * file_name_83);
#endif

static uint8 fat_get_fs_free_16_callback(uint8 * buffer, offset_t offset,
					 void *p);
#if FAT_FAT32_SUPPORT
static uint8 fat_get_fs_free_32_callback(uint8 * buffer, offset_t offset,
					 void *p);
#endif

#if FAT_WRITE_SUPPORT
static cluster_t fat_append_clusters(struct fat_fs_struct *fs,
				     cluster_t cluster_num,
				     cluster_t count);
static uint8 fat_free_clusters(struct fat_fs_struct *fs,
			       cluster_t cluster_num);
static uint8 fat_terminate_clusters(struct fat_fs_struct *fs,
				    cluster_t cluster_num);
static uint8 fat_clear_cluster(const struct fat_fs_struct *fs,
			       cluster_t cluster_num);
static uintptr_t fat_clear_cluster_callback(uint8 * buffer,
					    offset_t offset, void *p);
static offset_t fat_find_offset_for_dir_entry(struct fat_fs_struct *fs,
					      const struct fat_dir_struct
					      *parent,
					      const struct
					      fat_dir_entry_struct
					      *dir_entry);
static uint8 fat_write_dir_entry(const struct fat_fs_struct *fs,
				 struct fat_dir_entry_struct *dir_entry);
#if FAT_DATETIME_SUPPORT
static void fat_set_file_modification_date(struct fat_dir_entry_struct
					   *dir_entry, uint16 year,
					   uint8 month, uint8 day);
static void fat_set_file_modification_time(struct fat_dir_entry_struct
					   *dir_entry, uint8 hour,
					   uint8 min, uint8 sec);
#endif
#endif

/**
 * \ingroup fat_fs
 * Opens a FAT filesystem.
 *
 * \param[in] partition Discriptor of partition on which the filesystem resides.
 * \returns 0 on error, a FAT filesystem descriptor on success.
 * \see fat_close
 */
struct fat_fs_struct *fat_open(struct partition_struct *partition)
{
	if (!partition ||
#if FAT_WRITE_SUPPORT
	    !partition->device_write || !partition->device_write_interval
#else
	    0
#endif
	    )
		return 0;

#if USE_DYNAMIC_MEMORY
	struct fat_fs_struct *fs = malloc(sizeof(*fs));
	if (!fs)
		return 0;
#else
	struct fat_fs_struct *fs = fat_fs_handles;
	uint8 i;
	for (i = 0; i < FAT_FS_COUNT; ++i) {
		if (!fs->partition)
			break;

		++fs;
	}
	if (i >= FAT_FS_COUNT)
		return 0;
#endif

	memset(fs, 0, sizeof(*fs));

	fs->partition = partition;
	if (!fat_read_header(fs)) {
#if USE_DYNAMIC_MEMORY
		free(fs);
#else
		fs->partition = 0;
#endif
		return 0;
	}

	return fs;
}

/**
 * \ingroup fat_fs
 * Closes a FAT filesystem.
 *
 * When this function returns, the given filesystem descriptor
 * will be invalid.
 *
 * \param[in] fs The filesystem to close.
 * \see fat_open
 */
void fat_close(struct fat_fs_struct *fs)
{
	if (!fs)
		return;

#if USE_DYNAMIC_MEMORY
	free(fs);
#else
	fs->partition = 0;
#endif
}

/**
 * \ingroup fat_fs
 * Reads and parses the header of a FAT filesystem.
 *
 * \param[in,out] fs The filesystem for which to parse the header.
 * \returns 0 on failure, 1 on success.
 */
uint8 fat_read_header(struct fat_fs_struct * fs)
{
	if (!fs)
		return 0;

	struct partition_struct *partition = fs->partition;
	if (!partition)
		return 0;

	/* read fat parameters */
#if FAT_FAT32_SUPPORT
	uint8 buffer[37];
#else
	uint8 buffer[25];
#endif
	offset_t partition_offset = (offset_t) partition->offset * 512;
	if (!partition->
	    device_read(partition_offset + 0x0b, buffer, sizeof(buffer)))
		return 0;

	uint16 bytes_per_sector = read16(&buffer[0x00]);
	uint16 reserved_sectors = read16(&buffer[0x03]);
	uint8 sectors_per_cluster = buffer[0x02];
	uint8 fat_copies = buffer[0x05];
	uint16 max_root_entries = read16(&buffer[0x06]);
	uint16 sector_count_16 = read16(&buffer[0x08]);
	uint16 sectors_per_fat = read16(&buffer[0x0b]);
	uint32 sector_count = read32(&buffer[0x15]);
#if FAT_FAT32_SUPPORT
	uint32 sectors_per_fat32 = read32(&buffer[0x19]);
	uint32 cluster_root_dir = read32(&buffer[0x21]);
#endif

	if (sector_count == 0) {
		if (sector_count_16 == 0)
			/* illegal volume size */
			return 0;
		else
			sector_count = sector_count_16;
	}
#if FAT_FAT32_SUPPORT
	if (sectors_per_fat != 0)
		sectors_per_fat32 = sectors_per_fat;
	else if (sectors_per_fat32 == 0)
		/* this is neither FAT16 nor FAT32 */
		return 0;
#else
	if (sectors_per_fat == 0)
		/* this is not a FAT16 */
		return 0;
#endif

	/* determine the type of FAT we have here */
	uint32 data_sector_count = sector_count - reserved_sectors
#if FAT_FAT32_SUPPORT
	    - sectors_per_fat32 * fat_copies
#else
	    - (uint32) sectors_per_fat * fat_copies
#endif
	    -
	    ((max_root_entries * 32 + bytes_per_sector -
	      1) / bytes_per_sector);
	uint32 data_cluster_count =
	    data_sector_count / sectors_per_cluster;
	if (data_cluster_count < 4085)
		/* this is a FAT12, not supported */
		return 0;
	else if (data_cluster_count < 65525)
		/* this is a FAT16 */
		partition->type = PARTITION_TYPE_FAT16;
	else
		/* this is a FAT32 */
		partition->type = PARTITION_TYPE_FAT32;

	/* fill header information */
	struct fat_header_struct *header = &fs->header;
	memset(header, 0, sizeof(*header));

	header->size = (offset_t) sector_count *bytes_per_sector;

	header->fat_offset =	/* jump to partition */
	    partition_offset +
	    /* jump to fat */
	(offset_t) reserved_sectors *bytes_per_sector;
	header->fat_size =
	    (data_cluster_count + 2) * (partition->type ==
					PARTITION_TYPE_FAT16 ? 2 : 4);

	header->sector_size = bytes_per_sector;
	header->cluster_size =
	    (uint16) bytes_per_sector *sectors_per_cluster;

#if FAT_FAT32_SUPPORT
	if (partition->type == PARTITION_TYPE_FAT16)
#endif
	{
		header->root_dir_offset =	/* jump to fats */
		    header->fat_offset +
		    /* jump to root directory entries */
		(offset_t) fat_copies *sectors_per_fat * bytes_per_sector;

		header->cluster_zero_offset =	/* jump to root directory entries */
		    header->root_dir_offset +
		    /* skip root directory entries */
		(offset_t) max_root_entries *32;
	}
#if FAT_FAT32_SUPPORT
	else {
		header->cluster_zero_offset =	/* jump to fats */
		    header->fat_offset +
		    /* skip fats */
		(offset_t) fat_copies *sectors_per_fat32 *
		    bytes_per_sector;

		header->root_dir_cluster = cluster_root_dir;
	}
#endif

	return 1;
}

/**
 * \ingroup fat_fs
 * Retrieves the next following cluster of a given cluster.
 *
 * Using the filesystem file allocation table, this function returns
 * the number of the cluster containing the data directly following
 * the data within the cluster with the given number.
 *
 * \param[in] fs The filesystem for which to determine the next cluster.
 * \param[in] cluster_num The number of the cluster for which to determine its successor.
 * \returns The wanted cluster number, or 0 on error.
 */
cluster_t fat_get_next_cluster(const struct fat_fs_struct * fs,
			       cluster_t cluster_num)
{
	if (!fs || cluster_num < 2)
		return 0;

#if FAT_FAT32_SUPPORT
	if (fs->partition->type == PARTITION_TYPE_FAT32) {
		/* read appropriate fat entry */
		uint32 fat_entry;
		if (!fs->partition->
		    device_read(fs->header.fat_offset +
				(offset_t) cluster_num * sizeof(fat_entry),
				(uint8 *) & fat_entry, sizeof(fat_entry)))
			return 0;

		/* determine next cluster from fat */
		cluster_num = ltoh32(fat_entry);

		if (cluster_num == FAT32_CLUSTER_FREE ||
		    cluster_num == FAT32_CLUSTER_BAD ||
		    (cluster_num >= FAT32_CLUSTER_RESERVED_MIN
		     && cluster_num <= FAT32_CLUSTER_RESERVED_MAX)
		    || (cluster_num >= FAT32_CLUSTER_LAST_MIN
			&& cluster_num <= FAT32_CLUSTER_LAST_MAX))
			return 0;
	} else
#endif
	{
		/* read appropriate fat entry */
		uint16 fat_entry;
		if (!fs->partition->
		    device_read(fs->header.fat_offset +
				(offset_t) cluster_num * sizeof(fat_entry),
				(uint8 *) & fat_entry, sizeof(fat_entry)))
			return 0;

		/* determine next cluster from fat */
		cluster_num = ltoh16(fat_entry);

		if (cluster_num == FAT16_CLUSTER_FREE ||
		    cluster_num == FAT16_CLUSTER_BAD ||
		    (cluster_num >= FAT16_CLUSTER_RESERVED_MIN
		     && cluster_num <= FAT16_CLUSTER_RESERVED_MAX)
		    || (cluster_num >= FAT16_CLUSTER_LAST_MIN
			&& cluster_num <= FAT16_CLUSTER_LAST_MAX))
			return 0;
	}

	return cluster_num;
}

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_fs
 * Appends a new cluster chain to an existing one.
 *
 * Set cluster_num to zero to create a completely new one.
 *
 * \param[in] fs The file system on which to operate.
 * \param[in] cluster_num The cluster to which to append the new chain.
 * \param[in] count The number of clusters to allocate.
 * \returns 0 on failure, the number of the first new cluster on success.
 */
cluster_t fat_append_clusters(struct fat_fs_struct * fs,
			      cluster_t cluster_num, cluster_t count)
{
	if (!fs)
		return 0;

	device_read_t device_read = fs->partition->device_read;
	device_write_t device_write = fs->partition->device_write;
	offset_t fat_offset = fs->header.fat_offset;
	cluster_t count_left = count;
	cluster_t cluster_current = fs->cluster_free;
	cluster_t cluster_next = 0;
	cluster_t cluster_count;
	uint16 fat_entry16;
#if FAT_FAT32_SUPPORT
	uint32 fat_entry32;
	uint8 is_fat32 = (fs->partition->type == PARTITION_TYPE_FAT32);

	if (is_fat32)
		cluster_count = fs->header.fat_size / sizeof(fat_entry32);
	else
#endif
		cluster_count = fs->header.fat_size / sizeof(fat_entry16);

	fs->cluster_free = 0;
	cluster_t cluster_left;
	for (cluster_left = cluster_count; cluster_left > 0;
	     --cluster_left, ++cluster_current) {
		if (cluster_current < 2
		    || cluster_current >= cluster_count)
			cluster_current = 2;

#if FAT_FAT32_SUPPORT
		if (is_fat32) {
			if (!device_read
			    (fat_offset +
			     (offset_t) cluster_current *
			     sizeof(fat_entry32), (uint8 *) & fat_entry32,
			     sizeof(fat_entry32)))
				return 0;
		} else
#endif
		{
			if (!device_read
			    (fat_offset +
			     (offset_t) cluster_current *
			     sizeof(fat_entry16), (uint8 *) & fat_entry16,
			     sizeof(fat_entry16)))
				return 0;
		}

#if FAT_FAT32_SUPPORT
		if (is_fat32) {
			/* check if this is a free cluster */
			if (fat_entry32 != HTOL32(FAT32_CLUSTER_FREE))
				continue;

			/* If we don't need this free cluster for the
			 * current allocation, we keep it in mind for
			 * the next time.
			 */
			if (count_left == 0) {
				fs->cluster_free = cluster_current;
				break;
			}

			/* allocate cluster */
			if (cluster_next == 0)
				fat_entry32 =
				    HTOL32(FAT32_CLUSTER_LAST_MAX);
			else
				fat_entry32 = htol32(cluster_next);

			if (!device_write
			    (fat_offset +
			     (offset_t) cluster_current *
			     sizeof(fat_entry32), (uint8 *) & fat_entry32,
			     sizeof(fat_entry32)))
				break;
		} else
#endif
		{
			/* check if this is a free cluster */
			if (fat_entry16 != HTOL16(FAT16_CLUSTER_FREE))
				continue;

			/* If we don't need this free cluster for the
			 * current allocation, we keep it in mind for
			 * the next time.
			 */
			if (count_left == 0) {
				fs->cluster_free = cluster_current;
				break;
			}

			/* allocate cluster */
			if (cluster_next == 0)
				fat_entry16 =
				    HTOL16(FAT16_CLUSTER_LAST_MAX);
			else
				fat_entry16 =
				    htol16((uint16) cluster_next);

			if (!device_write
			    (fat_offset +
			     (offset_t) cluster_current *
			     sizeof(fat_entry16), (uint8 *) & fat_entry16,
			     sizeof(fat_entry16)))
				break;
		}

		cluster_next = cluster_current;
		--count_left;
	}

	do {
		if (count_left > 0)
			break;

		/* We allocated a new cluster chain. Now join
		 * it with the existing one (if any).
		 */
		if (cluster_num >= 2) {
#if FAT_FAT32_SUPPORT
			if (is_fat32) {
				fat_entry32 = htol32(cluster_next);

				if (!device_write
				    (fat_offset +
				     (offset_t) cluster_num *
				     sizeof(fat_entry32),
				     (uint8 *) & fat_entry32,
				     sizeof(fat_entry32)))
					break;
			} else
#endif
			{
				fat_entry16 =
				    htol16((uint16) cluster_next);

				if (!device_write
				    (fat_offset +
				     (offset_t) cluster_num *
				     sizeof(fat_entry16),
				     (uint8 *) & fat_entry16,
				     sizeof(fat_entry16)))
					break;
			}
		}

		return cluster_next;

	} while (0);

	/* No space left on device or writing error.
	 * Free up all clusters already allocated.
	 */
	fat_free_clusters(fs, cluster_next);

	return 0;
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_fs
 * Frees a cluster chain, or a part thereof.
 *
 * Marks the specified cluster and all clusters which are sequentially
 * referenced by it as free. They may then be used again for future
 * file allocations.
 *
 * \note If this function is used for freeing just a part of a cluster
 *       chain, the new end of the chain is not correctly terminated
 *       within the FAT. Use fat_terminate_clusters() instead.
 *
 * \param[in] fs The filesystem on which to operate.
 * \param[in] cluster_num The starting cluster of the chain which to free.
 * \returns 0 on failure, 1 on success.
 * \see fat_terminate_clusters
 */
uint8 fat_free_clusters(struct fat_fs_struct * fs, cluster_t cluster_num)
{
	if (!fs || cluster_num < 2)
		return 0;

	offset_t fat_offset = fs->header.fat_offset;
#if FAT_FAT32_SUPPORT
	if (fs->partition->type == PARTITION_TYPE_FAT32) {
		uint32 fat_entry;
		while (cluster_num) {
			if (!fs->partition->
			    device_read(fat_offset +
					(offset_t) cluster_num *
					sizeof(fat_entry),
					(uint8 *) & fat_entry,
					sizeof(fat_entry)))
				return 0;

			/* get next cluster of current cluster before freeing current cluster */
			uint32 cluster_num_next = ltoh32(fat_entry);

			if (cluster_num_next == FAT32_CLUSTER_FREE)
				return 1;
			if (cluster_num_next == FAT32_CLUSTER_BAD ||
			    (cluster_num_next >= FAT32_CLUSTER_RESERVED_MIN
			     && cluster_num_next <=
			     FAT32_CLUSTER_RESERVED_MAX)
			    )
				return 0;
			if (cluster_num_next >= FAT32_CLUSTER_LAST_MIN
			    && cluster_num_next <= FAT32_CLUSTER_LAST_MAX)
				cluster_num_next = 0;

			/* We know we will free the cluster, so remember it as
			 * free for the next allocation.
			 */
			if (!fs->cluster_free)
				fs->cluster_free = cluster_num;

			/* free cluster */
			fat_entry = HTOL32(FAT32_CLUSTER_FREE);
			fs->partition->device_write(fat_offset +
						    (offset_t) cluster_num
						    * sizeof(fat_entry),
						    (uint8 *) & fat_entry,
						    sizeof(fat_entry));

			/* We continue in any case here, even if freeing the cluster failed.
			 * The cluster is lost, but maybe we can still free up some later ones.
			 */

			cluster_num = cluster_num_next;
		}
	} else
#endif
	{
		uint16 fat_entry;
		while (cluster_num) {
			if (!fs->partition->
			    device_read(fat_offset +
					(offset_t) cluster_num *
					sizeof(fat_entry),
					(uint8 *) & fat_entry,
					sizeof(fat_entry)))
				return 0;

			/* get next cluster of current cluster before freeing current cluster */
			uint16 cluster_num_next = ltoh16(fat_entry);

			if (cluster_num_next == FAT16_CLUSTER_FREE)
				return 1;
			if (cluster_num_next == FAT16_CLUSTER_BAD ||
			    (cluster_num_next >= FAT16_CLUSTER_RESERVED_MIN
			     && cluster_num_next <=
			     FAT16_CLUSTER_RESERVED_MAX)
			    )
				return 0;
			if (cluster_num_next >= FAT16_CLUSTER_LAST_MIN
			    && cluster_num_next <= FAT16_CLUSTER_LAST_MAX)
				cluster_num_next = 0;

			/* free cluster */
			fat_entry = HTOL16(FAT16_CLUSTER_FREE);
			fs->partition->device_write(fat_offset +
						    (offset_t) cluster_num
						    * sizeof(fat_entry),
						    (uint8 *) & fat_entry,
						    sizeof(fat_entry));

			/* We continue in any case here, even if freeing the cluster failed.
			 * The cluster is lost, but maybe we can still free up some later ones.
			 */

			cluster_num = cluster_num_next;
		}
	}

	return 1;
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_fs
 * Frees a part of a cluster chain and correctly terminates the rest.
 *
 * Marks the specified cluster as the new end of a cluster chain and
 * frees all following clusters.
 *
 * \param[in] fs The filesystem on which to operate.
 * \param[in] cluster_num The new end of the cluster chain.
 * \returns 0 on failure, 1 on success.
 * \see fat_free_clusters
 */
uint8 fat_terminate_clusters(struct fat_fs_struct * fs,
			     cluster_t cluster_num)
{
	if (!fs || cluster_num < 2)
		return 0;

	/* fetch next cluster before overwriting the cluster entry */
	cluster_t cluster_num_next = fat_get_next_cluster(fs, cluster_num);

	/* mark cluster as the last one */
#if FAT_FAT32_SUPPORT
	if (fs->partition->type == PARTITION_TYPE_FAT32) {
		uint32 fat_entry = HTOL32(FAT32_CLUSTER_LAST_MAX);
		if (!fs->partition->
		    device_write(fs->header.fat_offset +
				 (offset_t) cluster_num *
				 sizeof(fat_entry), (uint8 *) & fat_entry,
				 sizeof(fat_entry)))
			return 0;
	} else
#endif
	{
		uint16 fat_entry = HTOL16(FAT16_CLUSTER_LAST_MAX);
		if (!fs->partition->
		    device_write(fs->header.fat_offset +
				 (offset_t) cluster_num *
				 sizeof(fat_entry), (uint8 *) & fat_entry,
				 sizeof(fat_entry)))
			return 0;
	}

	/* free remaining clusters */
	if (cluster_num_next)
		return fat_free_clusters(fs, cluster_num_next);
	else
		return 1;
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_fs
 * Clears a single cluster.
 *
 * The complete cluster is filled with zeros.
 *
 * \param[in] fs The filesystem on which to operate.
 * \param[in] cluster_num The cluster to clear.
 * \returns 0 on failure, 1 on success.
 */
uint8 fat_clear_cluster(const struct fat_fs_struct * fs,
			cluster_t cluster_num)
{
	if (cluster_num < 2)
		return 0;

	offset_t cluster_offset = fat_cluster_offset(fs, cluster_num);

	uint8 zero[16];
	memset(zero, 0, sizeof(zero));
	return fs->partition->device_write_interval(cluster_offset,
						    zero,
						    fs->header.
						    cluster_size,
						    fat_clear_cluster_callback,
						    0);
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_fs
 * Callback function for clearing a cluster.
 */
uintptr_t fat_clear_cluster_callback(uint8 * buffer, offset_t offset,
				     void *p)
{
	return 16;
}
#endif

/**
 * \ingroup fat_fs
 * Calculates the offset of the specified cluster.
 *
 * \param[in] fs The filesystem on which to operate.
 * \param[in] cluster_num The cluster whose offset to calculate.
 * \returns The cluster offset.
 */
offset_t fat_cluster_offset(const struct fat_fs_struct * fs,
			    cluster_t cluster_num)
{
	if (!fs || cluster_num < 2)
		return 0;

	return fs->header.cluster_zero_offset + (offset_t) (cluster_num -
							    2) *
	    fs->header.cluster_size;
}

/**
 * \ingroup fat_file
 * Retrieves the directory entry of a path.
 *
 * The given path may both describe a file or a directory.
 *
 * \param[in] fs The FAT filesystem on which to search.
 * \param[in] path The path of which to read the directory entry.
 * \param[out] dir_entry The directory entry to fill.
 * \returns 0 on failure, 1 on success.
 * \see fat_read_dir
 */
uint8 fat_get_dir_entry_of_path(struct fat_fs_struct * fs,
				const char *path,
				struct fat_dir_entry_struct * dir_entry)
{
	if (!fs || !path || path[0] == '\0' || !dir_entry)
		return 0;

	if (path[0] == '/')
		++path;

	/* begin with the root directory */
	memset(dir_entry, 0, sizeof(*dir_entry));
	dir_entry->attributes = FAT_ATTRIB_DIR;

	while (1) {
		if (path[0] == '\0')
			return 1;

		struct fat_dir_struct *dd = fat_open_dir(fs, dir_entry);
		if (!dd)
			break;

		/* extract the next hierarchy we will search for */
		const char *sub_path = strchr(path, '/');
		uint8 length_to_sep;
		if (sub_path) {
			length_to_sep = sub_path - path;
			++sub_path;
		} else {
			length_to_sep = strlen(path);
			sub_path = path + length_to_sep;
		}

		/* read directory entries */
		while (fat_read_dir(dd, dir_entry)) {
			/* check if we have found the next hierarchy */
			if ((strlen(dir_entry->long_name) != length_to_sep
			     || strncmp(path, dir_entry->long_name,
					length_to_sep) != 0))
				continue;

			fat_close_dir(dd);
			dd = 0;

			if (path[length_to_sep] == '\0')
				/* we iterated through the whole path and have found the file */
				return 1;

			if (dir_entry->attributes & FAT_ATTRIB_DIR) {
				/* we found a parent directory of the file we are searching for */
				path = sub_path;
				break;
			}

			/* a parent of the file exists, but not the file itself */
			return 0;
		}

		fat_close_dir(dd);
	}

	return 0;
}

/**
 * \ingroup fat_file
 * Opens a file on a FAT filesystem.
 *
 * \param[in] fs The filesystem on which the file to open lies.
 * \param[in] dir_entry The directory entry of the file to open.
 * \returns The file handle, or 0 on failure.
 * \see fat_close_file
 */
struct fat_file_struct *fat_open_file(struct fat_fs_struct *fs,
				      const struct fat_dir_entry_struct
				      *dir_entry)
{
	if (!fs || !dir_entry || (dir_entry->attributes & FAT_ATTRIB_DIR))
		return 0;

#if USE_DYNAMIC_MEMORY
	struct fat_file_struct *fd = malloc(sizeof(*fd));
	if (!fd)
		return 0;
#else
	struct fat_file_struct *fd = fat_file_handles;
	uint8 i;
	for (i = 0; i < FAT_FILE_COUNT; ++i) {
		if (!fd->fs)
			break;

		++fd;
	}
	if (i >= FAT_FILE_COUNT)
		return 0;
#endif

	memcpy(&fd->dir_entry, dir_entry, sizeof(*dir_entry));
	fd->fs = fs;
	fd->pos = 0;
	fd->pos_cluster = dir_entry->cluster;

	return fd;
}

/**
 * \ingroup fat_file
 * Closes a file.
 *
 * \param[in] fd The file handle of the file to close.
 * \see fat_open_file
 */
void fat_close_file(struct fat_file_struct *fd)
{
	if (fd) {
#if FAT_DELAY_DIRENTRY_UPDATE
		/* write directory entry */
		fat_write_dir_entry(fd->fs, &fd->dir_entry);
#endif

#if USE_DYNAMIC_MEMORY
		free(fd);
#else
		fd->fs = 0;
#endif
	}
}

/**
 * \ingroup fat_file
 * Reads data from a file.
 * 
 * The data requested is read from the current file location.
 *
 * \param[in] fd The file handle of the file from which to read.
 * \param[out] buffer The buffer into which to write.
 * \param[in] buffer_len The amount of data to read.
 * \returns The number of bytes read, 0 on end of file, or -1 on failure.
 * \see fat_write_file
 */
intptr_t fat_read_file(struct fat_file_struct *fd, uint8 * buffer,
		       uintptr_t buffer_len)
{
	/* check arguments */
	if (!fd || !buffer || buffer_len < 1)
		return -1;

	/* determine number of bytes to read */
	if (fd->pos + buffer_len > fd->dir_entry.file_size)
		buffer_len = fd->dir_entry.file_size - fd->pos;
	if (buffer_len == 0)
		return 0;

	uint16 cluster_size = fd->fs->header.cluster_size;
	cluster_t cluster_num = fd->pos_cluster;
	uintptr_t buffer_left = buffer_len;
	uint16 first_cluster_offset =
	    (uint16) (fd->pos & (cluster_size - 1));

	/* find cluster in which to start reading */
	if (!cluster_num) {
		cluster_num = fd->dir_entry.cluster;

		if (!cluster_num) {
			if (!fd->pos)
				return 0;
			else
				return -1;
		}

		if (fd->pos) {
			uint32 pos = fd->pos;
			while (pos >= cluster_size) {
				pos -= cluster_size;
				cluster_num =
				    fat_get_next_cluster(fd->fs,
							 cluster_num);
				if (!cluster_num)
					return -1;
			}
		}
	}

	/* read data */
	do {
		/* calculate data size to copy from cluster */
		offset_t cluster_offset =
		    fat_cluster_offset(fd->fs,
				       cluster_num) + first_cluster_offset;
		uint16 copy_length = cluster_size - first_cluster_offset;
		if (copy_length > buffer_left)
			copy_length = buffer_left;

		/* read data */
		if (!fd->fs->partition->
		    device_read(cluster_offset, buffer, copy_length))
			return buffer_len - buffer_left;

		/* calculate new file position */
		buffer += copy_length;
		buffer_left -= copy_length;
		fd->pos += copy_length;

		if (first_cluster_offset + copy_length >= cluster_size) {
			/* we are on a cluster boundary, so get the next cluster */
			if ((cluster_num =
			     fat_get_next_cluster(fd->fs, cluster_num))) {
				first_cluster_offset = 0;
			} else {
				fd->pos_cluster = 0;
				return buffer_len - buffer_left;
			}
		}

		fd->pos_cluster = cluster_num;

	} while (buffer_left > 0);	/* check if we are done */

	return buffer_len;
}

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_file
 * Writes data to a file.
 * 
 * The data is written to the current file location.
 *
 * \param[in] fd The file handle of the file to which to write.
 * \param[in] buffer The buffer from which to read the data to be written.
 * \param[in] buffer_len The amount of data to write.
 * \returns The number of bytes written (0 or something less than \c buffer_len on disk full) or -1 on failure.
 * \see fat_read_file
 */
intptr_t fat_write_file(struct fat_file_struct * fd, const uint8 * buffer,
			uintptr_t buffer_len)
{
	/* check arguments */
	if (!fd || !buffer || buffer_len < 1)
		return -1;
	if (fd->pos > fd->dir_entry.file_size)
		return -1;

	uint16 cluster_size = fd->fs->header.cluster_size;
	cluster_t cluster_num = fd->pos_cluster;
	uintptr_t buffer_left = buffer_len;
	uint16 first_cluster_offset =
	    (uint16) (fd->pos & (cluster_size - 1));

	/* find cluster in which to start writing */
	if (!cluster_num) {
		cluster_num = fd->dir_entry.cluster;

		if (!cluster_num) {
			if (!fd->pos) {
				/* empty file */
				fd->dir_entry.cluster = cluster_num =
				    fat_append_clusters(fd->fs, 0, 1);
				if (!cluster_num)
					return 0;
			} else {
				return -1;
			}
		}

		if (fd->pos) {
			uint32 pos = fd->pos;
			cluster_t cluster_num_next;
			while (pos >= cluster_size) {
				pos -= cluster_size;
				cluster_num_next =
				    fat_get_next_cluster(fd->fs,
							 cluster_num);
				if (!cluster_num_next) {
					if (pos != 0)
						return -1;	/* current file position points beyond end of file */

					/* the file exactly ends on a cluster boundary, and we append to it */
					cluster_num_next =
					    fat_append_clusters(fd->fs,
								cluster_num,
								1);
					if (!cluster_num_next)
						return 0;
				}

				cluster_num = cluster_num_next;
			}
		}
	}

	/* write data */
	do {
		/* calculate data size to write to cluster */
		offset_t cluster_offset =
		    fat_cluster_offset(fd->fs,
				       cluster_num) + first_cluster_offset;
		uint16 write_length = cluster_size - first_cluster_offset;
		if (write_length > buffer_left)
			write_length = buffer_left;

		/* write data which fits into the current cluster */
		if (!fd->fs->partition->
		    device_write(cluster_offset, buffer, write_length))
			break;

		/* calculate new file position */
		buffer += write_length;
		buffer_left -= write_length;
		fd->pos += write_length;

		if (first_cluster_offset + write_length >= cluster_size) {
			/* we are on a cluster boundary, so get the next cluster */
			cluster_t cluster_num_next =
			    fat_get_next_cluster(fd->fs, cluster_num);
			if (!cluster_num_next && buffer_left > 0)
				/* we reached the last cluster, append a new one */
				cluster_num_next =
				    fat_append_clusters(fd->fs,
							cluster_num, 1);
			if (!cluster_num_next) {
				fd->pos_cluster = 0;
				break;
			}

			cluster_num = cluster_num_next;
			first_cluster_offset = 0;
		}

		fd->pos_cluster = cluster_num;

	} while (buffer_left > 0);	/* check if we are done */

	/* update directory entry */
	if (fd->pos > fd->dir_entry.file_size) {
#if !FAT_DELAY_DIRENTRY_UPDATE
		uint32 size_old = fd->dir_entry.file_size;
#endif

		/* update file size */
		fd->dir_entry.file_size = fd->pos;

#if !FAT_DELAY_DIRENTRY_UPDATE
		/* write directory entry */
		if (!fat_write_dir_entry(fd->fs, &fd->dir_entry)) {
			/* We do not return an error here since we actually wrote
			 * some data to disk. So we calculate the amount of data
			 * we wrote to disk and which lies within the old file size.
			 */
			buffer_left = fd->pos - size_old;
			fd->pos = size_old;
		}
#endif
	}

	return buffer_len - buffer_left;
}
#endif

/**
 * \ingroup fat_file
 * Repositions the read/write file offset.
 *
 * Changes the file offset where the next call to fat_read_file()
 * or fat_write_file() starts reading/writing.
 *
 * If the new offset is beyond the end of the file, fat_resize_file()
 * is implicitly called, i.e. the file is expanded.
 *
 * The new offset can be given in different ways determined by
 * the \c whence parameter:
 * - \b FAT_SEEK_SET: \c *offset is relative to the beginning of the file.
 * - \b FAT_SEEK_CUR: \c *offset is relative to the current file position.
 * - \b FAT_SEEK_END: \c *offset is relative to the end of the file.
 *
 * The resulting absolute offset is written to the location the \c offset
 * parameter points to.
 *
 * Calling this function can also be used to retrieve the current file position:
   \code
   sint32 file_pos = 0;
   if(!fat_seek_file(fd, &file_pos, FAT_SEEK_CUR))
   {
       // error
   }
   // file_pos now contains the absolute file position
   \endcode
 * 
 * \param[in] fd The file decriptor of the file on which to seek.
 * \param[in,out] offset A pointer to the new offset, as affected by the \c whence
 *                   parameter. The function writes the new absolute offset
 *                   to this location before it returns.
 * \param[in] whence Affects the way \c offset is interpreted, see above.
 * \returns 0 on failure, 1 on success.
 */
uint8 fat_seek_file(struct fat_file_struct * fd, sint32 * offset,
		    uint8 whence)
{
	if (!fd || !offset)
		return 0;

	uint32 new_pos = fd->pos;
	switch (whence) {
	case FAT_SEEK_SET:
		new_pos = *offset;
		break;
	case FAT_SEEK_CUR:
		new_pos += *offset;
		break;
	case FAT_SEEK_END:
		new_pos = fd->dir_entry.file_size + *offset;
		break;
	default:
		return 0;
	}

	if (new_pos > fd->dir_entry.file_size
#if FAT_WRITE_SUPPORT
	    && !fat_resize_file(fd, new_pos)
#endif
	    )
		return 0;

	fd->pos = new_pos;
	fd->pos_cluster = 0;

	*offset = (sint32) new_pos;
	return 1;
}

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_file
 * Resizes a file to have a specific size.
 *
 * Enlarges or shrinks the file pointed to by the file descriptor to have
 * exactly the specified size.
 *
 * If the file is truncated, all bytes having an equal or larger offset
 * than the given size are lost. If the file is expanded, the additional
 * bytes are allocated.
 *
 * \note Please be aware that this function just allocates or deallocates disk
 * space, it does not explicitely clear it. To avoid data leakage, this
 * must be done manually.
 *
 * \param[in] fd The file decriptor of the file which to resize.
 * \param[in] size The new size of the file.
 * \returns 0 on failure, 1 on success.
 */
uint8 fat_resize_file(struct fat_file_struct * fd, uint32 size)
{
	if (!fd)
		return 0;

	cluster_t cluster_num = fd->dir_entry.cluster;
	uint16 cluster_size = fd->fs->header.cluster_size;
	uint32 size_new = size;

	do {
		if (cluster_num == 0 && size_new == 0)
			/* the file stays empty */
			break;

		/* seek to the next cluster as long as we need the space */
		while (size_new > cluster_size) {
			/* get next cluster of file */
			cluster_t cluster_num_next =
			    fat_get_next_cluster(fd->fs, cluster_num);
			if (cluster_num_next) {
				cluster_num = cluster_num_next;
				size_new -= cluster_size;
			} else {
				break;
			}
		}

		if (size_new > cluster_size || cluster_num == 0) {
			/* Allocate new cluster chain and append
			 * it to the existing one, if available.
			 */
			cluster_t cluster_count =
			    (size_new + cluster_size - 1) / cluster_size;
			cluster_t cluster_new_chain =
			    fat_append_clusters(fd->fs, cluster_num,
						cluster_count);
			if (!cluster_new_chain)
				return 0;

			if (!cluster_num) {
				cluster_num = cluster_new_chain;
				fd->dir_entry.cluster = cluster_num;
			}
		}

		/* write new directory entry */
		fd->dir_entry.file_size = size;
		if (size == 0)
			fd->dir_entry.cluster = 0;
		if (!fat_write_dir_entry(fd->fs, &fd->dir_entry))
			return 0;

		if (size == 0) {
			/* free all clusters of file */
			fat_free_clusters(fd->fs, cluster_num);
		} else if (size_new <= cluster_size) {
			/* free all clusters no longer needed */
			fat_terminate_clusters(fd->fs, cluster_num);
		}

	} while (0);

	/* correct file position */
	if (size < fd->pos) {
		fd->pos = size;
		fd->pos_cluster = 0;
	}

	return 1;
}
#endif

/**
 * \ingroup fat_dir
 * Opens a directory.
 *
 * \param[in] fs The filesystem on which the directory to open resides.
 * \param[in] dir_entry The directory entry which stands for the directory to open.
 * \returns An opaque directory descriptor on success, 0 on failure.
 * \see fat_close_dir
 */
struct fat_dir_struct *fat_open_dir(struct fat_fs_struct *fs,
				    const struct fat_dir_entry_struct
				    *dir_entry)
{
	if (!fs || !dir_entry || !(dir_entry->attributes & FAT_ATTRIB_DIR))
		return 0;

#if USE_DYNAMIC_MEMORY
	struct fat_dir_struct *dd = malloc(sizeof(*dd));
	if (!dd)
		return 0;
#else
	struct fat_dir_struct *dd = fat_dir_handles;
	uint8 i;
	for (i = 0; i < FAT_DIR_COUNT; ++i) {
		if (!dd->fs)
			break;

		++dd;
	}
	if (i >= FAT_DIR_COUNT)
		return 0;
#endif

	memcpy(&dd->dir_entry, dir_entry, sizeof(*dir_entry));
	dd->fs = fs;
	dd->entry_cluster = dir_entry->cluster;
	dd->entry_offset = 0;

	return dd;
}

/**
 * \ingroup fat_dir
 * Closes a directory descriptor.
 *
 * This function destroys a directory descriptor which was
 * previously obtained by calling fat_open_dir(). When this
 * function returns, the given descriptor will be invalid.
 *
 * \param[in] dd The directory descriptor to close.
 * \see fat_open_dir
 */
void fat_close_dir(struct fat_dir_struct *dd)
{
	if (dd)
#if USE_DYNAMIC_MEMORY
		free(dd);
#else
		dd->fs = 0;
#endif
}

/**
 * \ingroup fat_dir
 * Reads the next directory entry contained within a parent directory.
 *
 * \param[in] dd The descriptor of the parent directory from which to read the entry.
 * \param[out] dir_entry Pointer to a buffer into which to write the directory entry information.
 * \returns 0 on failure, 1 on success.
 * \see fat_reset_dir
 */
uint8 fat_read_dir(struct fat_dir_struct *dd,
		   struct fat_dir_entry_struct *dir_entry)
{
	if (!dd || !dir_entry)
		return 0;

	/* get current position of directory handle */
	struct fat_fs_struct *fs = dd->fs;
	const struct fat_header_struct *header = &fs->header;
	uint16 cluster_size = header->cluster_size;
	cluster_t cluster_num = dd->entry_cluster;
	uint16 cluster_offset = dd->entry_offset;
	struct fat_read_dir_callback_arg arg;

	if (cluster_offset >= cluster_size) {
		/* The latest call hit the border of the last cluster in
		 * the chain, but it still returned a directory entry.
		 * So we now reset the handle and signal the caller the
		 * end of the listing.
		 */
		fat_reset_dir(dd);
		return 0;
	}

	/* reset callback arguments */
	memset(&arg, 0, sizeof(arg));
	memset(dir_entry, 0, sizeof(*dir_entry));
	arg.dir_entry = dir_entry;

	/* check if we read from the root directory */
	if (cluster_num == 0) {
#if FAT_FAT32_SUPPORT
		if (fs->partition->type == PARTITION_TYPE_FAT32)
			cluster_num = header->root_dir_cluster;
		else
#endif
			cluster_size =
			    header->cluster_zero_offset -
			    header->root_dir_offset;
	}

	/* read entries */
	uint8 buffer[32];
	while (!arg.finished) {
		/* read directory entries up to the cluster border */
		uint16 cluster_left = cluster_size - cluster_offset;
		offset_t pos = cluster_offset;
		if (cluster_num == 0)
			pos += header->root_dir_offset;
		else
			pos += fat_cluster_offset(fs, cluster_num);

		arg.bytes_read = 0;
		if (!fs->partition->device_read_interval(pos,
							 buffer,
							 sizeof(buffer),
							 cluster_left,
							 fat_dir_entry_read_callback,
							 &arg)
		    )
			return 0;

		cluster_offset += arg.bytes_read;

		if (cluster_offset >= cluster_size) {
			/* we reached the cluster border and switch to the next cluster */

			/* get number of next cluster */
			if ((cluster_num =
			     fat_get_next_cluster(fs, cluster_num)) != 0) {
				cluster_offset = 0;
				continue;
			}

			/* we are at the end of the cluster chain */
			if (!arg.finished) {
				/* directory entry not found, reset directory handle */
				fat_reset_dir(dd);
				return 0;
			} else {
				/* The current execution of the function has been successful,
				 * so we can not signal an end of the directory listing to
				 * the caller, but must wait for the next call. So we keep an
				 * invalid cluster offset to mark this directory handle's
				 * traversal as finished.
				 */
			}

			break;
		}
	}

	dd->entry_cluster = cluster_num;
	dd->entry_offset = cluster_offset;

	return arg.finished;
}

/**
 * \ingroup fat_dir
 * Resets a directory handle.
 *
 * Resets the directory handle such that reading restarts
 * with the first directory entry.
 *
 * \param[in] dd The directory handle to reset.
 * \returns 0 on failure, 1 on success.
 * \see fat_read_dir
 */
uint8 fat_reset_dir(struct fat_dir_struct * dd)
{
	if (!dd)
		return 0;

	dd->entry_cluster = dd->dir_entry.cluster;
	dd->entry_offset = 0;
	return 1;
}

/**
 * \ingroup fat_fs
 * Callback function for reading a directory entry.
 *
 * Interprets a raw directory entry and puts the contained
 * information into a fat_dir_entry_struct structure.
 * 
 * For a single file there may exist multiple directory
 * entries. All except the last one are lfn entries, which
 * contain parts of the long filename. The last directory
 * entry is a traditional 8.3 style one. It contains all
 * other information like size, cluster, date and time.
 * 
 * \param[in] buffer A pointer to 32 bytes of raw data.
 * \param[in] offset The absolute offset of the raw data.
 * \param[in,out] p An argument structure controlling operation.
 * \returns 0 on failure or completion, 1 if reading has
 *          to be continued
 */
uint8 fat_dir_entry_read_callback(uint8 * buffer, offset_t offset, void *p)
{
	struct fat_read_dir_callback_arg *arg = p;
	struct fat_dir_entry_struct *dir_entry = arg->dir_entry;

	arg->bytes_read += 32;

	/* skip deleted or empty entries */
	if (buffer[0] == FAT_DIRENTRY_DELETED || !buffer[0]) {
#if FAT_LFN_SUPPORT
		arg->checksum = 0;
#endif
		return 1;
	}
#if !FAT_LFN_SUPPORT
	/* skip lfn entries */
	if (buffer[11] == 0x0f)
		return 1;
#endif

	char *long_name = dir_entry->long_name;
#if FAT_LFN_SUPPORT
	if (buffer[11] == 0x0f) {
		/* checksum validation */
		if (arg->checksum == 0 || arg->checksum != buffer[13]) {
			/* reset directory entry */
			memset(dir_entry, 0, sizeof(*dir_entry));

			arg->checksum = buffer[13];
			dir_entry->entry_offset = offset;
		}

		/* lfn supports unicode, but we do not, for now.
		 * So we assume pure ascii and read only every
		 * second byte.
		 */
		uint16 char_offset = ((buffer[0] & 0x3f) - 1) * 13;
		const uint8 char_mapping[] =
		    { 1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30 };
		uint8 i;
		for (i = 0;
		     i <= 12
		     && char_offset + i < sizeof(dir_entry->long_name) - 1;
		     ++i)
			long_name[char_offset + i] =
			    buffer[char_mapping[i]];

		return 1;
	} else
#endif
	{
#if FAT_LFN_SUPPORT
		/* if we do not have a long name or the previous lfn does not match, take the 8.3 name */
		if (long_name[0] == '\0'
		    || arg->checksum != fat_calc_83_checksum(buffer))
#endif
		{
			/* reset directory entry */
			memset(dir_entry, 0, sizeof(*dir_entry));
			dir_entry->entry_offset = offset;

			uint8 i;
			for (i = 0; i < 8; ++i) {
				if (buffer[i] == ' ')
					break;
				long_name[i] = buffer[i];

				/* Windows NT and later versions do not store lfn entries
				 * for 8.3 names which have a lowercase basename, extension
				 * or both when everything else is uppercase. They use two
				 * extra bits to signal a lowercase basename or extension.
				 */
				if ((buffer[12] & 0x08) && buffer[i] >= 'A'
				    && buffer[i] <= 'Z')
					long_name[i] += 'a' - 'A';
			}
			if (long_name[0] == 0x05)
				long_name[0] = (char) FAT_DIRENTRY_DELETED;

			if (buffer[8] != ' ') {
				long_name[i++] = '.';

				uint8 j = 8;
				for (; j < 11; ++j) {
					if (buffer[j] == ' ')
						break;
					long_name[i] = buffer[j];

					/* See above for the lowercase 8.3 name handling of
					 * Windows NT and later.
					 */
					if ((buffer[12] & 0x10)
					    && buffer[j] >= 'A'
					    && buffer[j] <= 'Z')
						long_name[i] += 'a' - 'A';

					++i;
				}
			}

			long_name[i] = '\0';
		}

		/* extract properties of file and store them within the structure */
		dir_entry->attributes = buffer[11];
		dir_entry->cluster = read16(&buffer[26]);
#if FAT_FAT32_SUPPORT
		dir_entry->cluster |=
		    ((cluster_t) read16(&buffer[20])) << 16;
#endif
		dir_entry->file_size = read32(&buffer[28]);

#if FAT_DATETIME_SUPPORT
		dir_entry->modification_time = read16(&buffer[22]);
		dir_entry->modification_date = read16(&buffer[24]);
#endif

		arg->finished = 1;
		return 0;
	}
}

#if DOXYGEN || FAT_LFN_SUPPORT
/**
 * \ingroup fat_fs
 * Calculates the checksum for 8.3 names used within the
 * corresponding lfn directory entries.
 *
 * \param[in] file_name_83 The 11-byte file name buffer.
 * \returns The checksum of the given file name.
 */
uint8 fat_calc_83_checksum(const uint8 * file_name_83)
{
	uint8 checksum = file_name_83[0];
	uint8 i;
	for (i = 1; i < 11; ++i)
		checksum =
		    ((checksum >> 1) | (checksum << 7)) + file_name_83[i];

	return checksum;
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_fs
 * Searches for space where to store a directory entry.
 *
 * \param[in] fs The filesystem on which to operate.
 * \param[in] parent The directory in which to search.
 * \param[in] dir_entry The directory entry for which to search space.
 * \returns 0 on failure, a device offset on success.
 */
offset_t fat_find_offset_for_dir_entry(struct fat_fs_struct * fs,
				       const struct fat_dir_struct *
				       parent,
				       const struct fat_dir_entry_struct *
				       dir_entry)
{
	if (!fs || !dir_entry)
		return 0;

	/* search for a place where to write the directory entry to disk */
#if FAT_LFN_SUPPORT
	uint8 free_dir_entries_needed =
	    (strlen(dir_entry->long_name) + 12) / 13 + 1;
	uint8 free_dir_entries_found = 0;
#endif
	cluster_t cluster_num = parent->dir_entry.cluster;
	offset_t dir_entry_offset = 0;
	offset_t offset = 0;
	offset_t offset_to = 0;
#if FAT_FAT32_SUPPORT
	uint8 is_fat32 = (fs->partition->type == PARTITION_TYPE_FAT32);
#endif

	if (cluster_num == 0) {
#if FAT_FAT32_SUPPORT
		if (is_fat32) {
			cluster_num = fs->header.root_dir_cluster;
		} else
#endif
		{
			/* we read/write from the root directory entry */
			offset = fs->header.root_dir_offset;
			offset_to = fs->header.cluster_zero_offset;
			dir_entry_offset = offset;
		}
	}

	while (1) {
		if (offset == offset_to) {
			if (cluster_num == 0)
				/* We iterated through the whole root directory and
				 * could not find enough space for the directory entry.
				 */
				return 0;

			if (offset) {
				/* We reached a cluster boundary and have to
				 * switch to the next cluster.
				 */

				cluster_t cluster_next =
				    fat_get_next_cluster(fs, cluster_num);
				if (!cluster_next) {
					cluster_next =
					    fat_append_clusters(fs,
								cluster_num,
								1);
					if (!cluster_next)
						return 0;

					/* we appended a new cluster and know it is free */
					dir_entry_offset =
					    fs->header.
					    cluster_zero_offset +
					    (offset_t) (cluster_next -
							2) *
					    fs->header.cluster_size;

					/* clear cluster to avoid garbage directory entries */
					fat_clear_cluster(fs,
							  cluster_next);

					break;
				}
				cluster_num = cluster_next;
			}

			offset = fat_cluster_offset(fs, cluster_num);
			offset_to = offset + fs->header.cluster_size;
			dir_entry_offset = offset;
#if FAT_LFN_SUPPORT
			free_dir_entries_found = 0;
#endif
		}

		/* read next lfn or 8.3 entry */
		uint8 first_char;
		if (!fs->partition->
		    device_read(offset, &first_char, sizeof(first_char)))
			return 0;

		/* check if we found a free directory entry */
		if (first_char == FAT_DIRENTRY_DELETED || !first_char) {
			/* check if we have the needed number of available entries */
#if FAT_LFN_SUPPORT
			++free_dir_entries_found;
			if (free_dir_entries_found >=
			    free_dir_entries_needed)
#endif
				break;

			offset += 32;
		} else {
			offset += 32;
			dir_entry_offset = offset;
#if FAT_LFN_SUPPORT
			free_dir_entries_found = 0;
#endif
		}
	}

	return dir_entry_offset;
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_fs
 * Writes a directory entry to disk.
 *
 * \note The file name is not checked for invalid characters.
 *
 * \note The generation of the short 8.3 file name is quite
 * simple. The first eight characters are used for the filename.
 * The extension, if any, is made up of the first three characters
 * following the last dot within the long filename. If the
 * filename (without the extension) is longer than eight characters,
 * the lower byte of the cluster number replaces the last two
 * characters to avoid name clashes. In any other case, it is your
 * responsibility to avoid name clashes.
 *
 * \param[in] fs The filesystem on which to operate.
 * \param[in] dir_entry The directory entry to write.
 * \returns 0 on failure, 1 on success.
 */
uint8 fat_write_dir_entry(const struct fat_fs_struct * fs,
			  struct fat_dir_entry_struct * dir_entry)
{
	if (!fs || !dir_entry)
		return 0;

#if FAT_DATETIME_SUPPORT
	{
		uint16 year;
		uint8 month;
		uint8 day;
		uint8 hour;
		uint8 min;
		uint8 sec;

		fat_get_datetime(&year, &month, &day, &hour, &min, &sec);
		fat_set_file_modification_date(dir_entry, year, month,
					       day);
		fat_set_file_modification_time(dir_entry, hour, min, sec);
	}
#endif

	device_write_t device_write = fs->partition->device_write;
	offset_t offset = dir_entry->entry_offset;
	const char *name = dir_entry->long_name;
	uint8 name_len = strlen(name);
#if FAT_LFN_SUPPORT
	uint8 lfn_entry_count = (name_len + 12) / 13;
#endif
	uint8 buffer[32];

	/* write 8.3 entry */

	/* generate 8.3 file name */
	memset(&buffer[0], ' ', 11);
	char *name_ext = strrchr(name, '.');
	if (name_ext && *++name_ext) {
		uint8 name_ext_len = strlen(name_ext);
		name_len -= name_ext_len + 1;

		if (name_ext_len > 3)
#if FAT_LFN_SUPPORT
			name_ext_len = 3;
#else
			return 0;
#endif

		memcpy(&buffer[8], name_ext, name_ext_len);
	}

	if (name_len <= 8) {
		memcpy(buffer, name, name_len);

#if FAT_LFN_SUPPORT
		/* For now, we create lfn entries for all files,
		 * except the "." and ".." directory references.
		 * This is to avoid difficulties with capitalization,
		 * as 8.3 filenames allow uppercase letters only.
		 *
		 * Theoretically it would be possible to leave
		 * the 8.3 entry alone if the basename and the
		 * extension have no mixed capitalization.
		 */
		if (name[0] == '.' &&
		    ((name[1] == '.' && name[2] == '\0') ||
		     name[1] == '\0')
		    )
			lfn_entry_count = 0;
#endif
	} else {
#if FAT_LFN_SUPPORT
		memcpy(buffer, name, 8);

		/* Minimize 8.3 name clashes by appending
		 * the lower byte of the cluster number.
		 */
		uint8 num = dir_entry->cluster & 0xff;

		buffer[6] =
		    (num < 0xa0) ? ('0' + (num >> 4)) : ('a' + (num >> 4));
		num &= 0x0f;
		buffer[7] = (num < 0x0a) ? ('0' + num) : ('a' + num);
#else
		return 0;
#endif
	}
	if (buffer[0] == FAT_DIRENTRY_DELETED)
		buffer[0] = 0x05;

	/* fill directory entry buffer */
	memset(&buffer[11], 0, sizeof(buffer) - 11);
	buffer[0x0b] = dir_entry->attributes;
#if FAT_DATETIME_SUPPORT
	write16(&buffer[0x16], dir_entry->modification_time);
	write16(&buffer[0x18], dir_entry->modification_date);
#endif
#if FAT_FAT32_SUPPORT
	write16(&buffer[0x14], (uint16) (dir_entry->cluster >> 16));
#endif
	write16(&buffer[0x1a], dir_entry->cluster);
	write32(&buffer[0x1c], dir_entry->file_size);

	/* write to disk */
#if FAT_LFN_SUPPORT
	if (!device_write
	    (offset + (uint16) lfn_entry_count * 32, buffer,
	     sizeof(buffer)))
#else
	if (!device_write(offset, buffer, sizeof(buffer)))
#endif
		return 0;

#if FAT_LFN_SUPPORT
	/* calculate checksum of 8.3 name */
	uint8 checksum = fat_calc_83_checksum(buffer);

	/* write lfn entries */
	uint8 lfn_entry;
	for (lfn_entry = lfn_entry_count; lfn_entry > 0; --lfn_entry) {
		memset(buffer, 0xff, sizeof(buffer));

		/* set file name */
		const char *long_name_curr = name + (lfn_entry - 1) * 13;
		uint8 i = 1;
		while (i < 0x1f) {
			buffer[i++] = *long_name_curr;
			buffer[i++] = 0;

			switch (i) {
			case 0x0b:
				i = 0x0e;
				break;
			case 0x1a:
				i = 0x1c;
				break;
			}

			if (!*long_name_curr++)
				break;
		}

		/* set index of lfn entry */
		buffer[0x00] = lfn_entry;
		if (lfn_entry == lfn_entry_count)
			buffer[0x00] |= FAT_DIRENTRY_LFNLAST;

		/* mark as lfn entry */
		buffer[0x0b] = 0x0f;

		/* set 8.3 checksum */
		buffer[0x0d] = checksum;

		/* clear reserved bytes */
		buffer[0x0c] = 0;
		buffer[0x1a] = 0;
		buffer[0x1b] = 0;

		/* write entry */
		device_write(offset, buffer, sizeof(buffer));

		offset += sizeof(buffer);
	}
#endif

	return 1;
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_file
 * Creates a file.
 *
 * Creates a file and obtains the directory entry of the
 * new file. If the file to create already exists, the
 * directory entry of the existing file will be returned
 * within the dir_entry parameter.
 *
 * \note The file name is not checked for invalid characters.
 *
 * \note The generation of the short 8.3 file name is quite
 * simple. The first eight characters are used for the filename.
 * The extension, if any, is made up of the first three characters
 * following the last dot within the long filename. If the
 * filename (without the extension) is longer than eight characters,
 * the lower byte of the cluster number replaces the last two
 * characters to avoid name clashes. In any other case, it is your
 * responsibility to avoid name clashes.
 *
 * \param[in] parent The handle of the directory in which to create the file.
 * \param[in] file The name of the file to create.
 * \param[out] dir_entry The directory entry to fill for the new (or existing) file.
 * \returns 0 on failure, 1 on success, 2 if the file already existed.
 * \see fat_delete_file
 */
uint8 fat_create_file(struct fat_dir_struct * parent, const char *file,
		      struct fat_dir_entry_struct * dir_entry)
{
	if (!parent || !file || !file[0] || !dir_entry)
		return 0;

	/* check if the file already exists */
	while (1) {
		if (!fat_read_dir(parent, dir_entry))
			break;

		if (strcmp(file, dir_entry->long_name) == 0) {
			fat_reset_dir(parent);
			return 2;
		}
	}

	struct fat_fs_struct *fs = parent->fs;

	/* prepare directory entry with values already known */
	memset(dir_entry, 0, sizeof(*dir_entry));
	strncpy(dir_entry->long_name, file,
		sizeof(dir_entry->long_name) - 1);

	/* find place where to store directory entry */
	if (!
	    (dir_entry->entry_offset =
	     fat_find_offset_for_dir_entry(fs, parent, dir_entry)))
		return 0;

	/* write directory entry to disk */
	if (!fat_write_dir_entry(fs, dir_entry))
		return 0;

	return 1;
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_file
 * Deletes a file or directory.
 *
 * If a directory is deleted without first deleting its
 * subdirectories and files, disk space occupied by these
 * files will get wasted as there is no chance to release
 * it and mark it as free.
 * 
 * \param[in] fs The filesystem on which to operate.
 * \param[in] dir_entry The directory entry of the file to delete.
 * \returns 0 on failure, 1 on success.
 * \see fat_create_file
 */
uint8 fat_delete_file(struct fat_fs_struct * fs,
		      struct fat_dir_entry_struct * dir_entry)
{
	if (!fs || !dir_entry)
		return 0;

	/* get offset of the file's directory entry */
	offset_t dir_entry_offset = dir_entry->entry_offset;
	if (!dir_entry_offset)
		return 0;

#if FAT_LFN_SUPPORT
	uint8 buffer[12];
	while (1) {
		/* read directory entry */
		if (!fs->partition->
		    device_read(dir_entry_offset, buffer, sizeof(buffer)))
			return 0;

		/* mark the directory entry as deleted */
		buffer[0] = FAT_DIRENTRY_DELETED;

		/* write back entry */
		if (!fs->partition->
		    device_write(dir_entry_offset, buffer, sizeof(buffer)))
			return 0;

		/* check if we deleted the whole entry */
		if (buffer[11] != 0x0f)
			break;

		dir_entry_offset += 32;
	}
#else
	/* mark the directory entry as deleted */
	uint8 first_char = FAT_DIRENTRY_DELETED;
	if (!fs->partition->device_write(dir_entry_offset, &first_char, 1))
		return 0;
#endif

	/* We deleted the directory entry. The next thing to do is
	 * marking all occupied clusters as free.
	 */
	return (dir_entry->cluster == 0
		|| fat_free_clusters(fs, dir_entry->cluster));
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_file
 * Moves or renames a file.
 *
 * Changes a file's name, optionally moving it into another
 * directory as well. Before calling this function, the
 * target file name must not exist. Moving a file to a
 * different filesystem (i.e. \a parent_new doesn't lie on
 * \a fs) is not supported.
 * 
 * After successfully renaming (and moving) the file, the
 * given directory entry is updated such that it points to
 * the file's new location.
 *
 * \note The notes which apply to fat_create_file() also
 * apply to this function.
 *
 * \param[in] fs The filesystem on which to operate.
 * \param[in,out] dir_entry The directory entry of the file to move.
 * \param[in] parent_new The handle of the new parent directory of the file.
 * \param[in] file_new The file's new name.
 * \returns 0 on failure, 1 on success.
 * \see fat_create_file, fat_delete_file, fat_move_dir
 */
uint8 fat_move_file(struct fat_fs_struct * fs,
		    struct fat_dir_entry_struct * dir_entry,
		    struct fat_dir_struct * parent_new,
		    const char *file_new)
{
	if (!fs || !dir_entry || !parent_new || (file_new && !file_new[0]))
		return 0;
	if (fs != parent_new->fs)
		return 0;

	/* use existing file name if none has been specified */
	if (!file_new)
		file_new = dir_entry->long_name;

	/* create file with new file name */
	struct fat_dir_entry_struct dir_entry_new;
	if (!fat_create_file(parent_new, file_new, &dir_entry_new))
		return 0;

	/* copy members of directory entry which do not change with rename */
	dir_entry_new.attributes = dir_entry->attributes;
#if FAT_DATETIME_SUPPORT
	dir_entry_new.modification_time = dir_entry->modification_time;
	dir_entry_new.modification_date = dir_entry->modification_date;
#endif
	dir_entry_new.cluster = dir_entry->cluster;
	dir_entry_new.file_size = dir_entry->file_size;

	/* make the new file name point to the old file's content */
	if (!fat_write_dir_entry(fs, &dir_entry_new)) {
		fat_delete_file(fs, &dir_entry_new);
		return 0;
	}

	/* delete the old file, but not its clusters, which have already been remapped above */
	dir_entry->cluster = 0;
	if (!fat_delete_file(fs, dir_entry))
		return 0;

	*dir_entry = dir_entry_new;
	return 1;
}
#endif

#if DOXYGEN || FAT_WRITE_SUPPORT
/**
 * \ingroup fat_dir
 * Creates a directory.
 *
 * Creates a directory and obtains its directory entry.
 * If the directory to create already exists, its
 * directory entry will be returned within the dir_entry
 * parameter.
 *
 * \note The notes which apply to fat_create_file() also
 * apply to this function.
 *
 * \param[in] parent The handle of the parent directory of the new directory.
 * \param[in] dir The name of the directory to create.
 * \param[out] dir_entry The directory entry to fill for the new directory.
 * \returns 0 on failure, 1 on success.
 * \see fat_delete_dir
 */
uint8 fat_create_dir(struct fat_dir_struct * parent, const char *dir,
		     struct fat_dir_entry_struct * dir_entry)
{
	if (!parent || !dir || !dir[0] || !dir_entry)
		return 0;

	/* check if the file or directory already exists */
	while (fat_read_dir(parent, dir_entry)) {
		if (strcmp(dir, dir_entry->long_name) == 0) {
			fat_reset_dir(parent);
			return 0;
		}
	}

	struct fat_fs_struct *fs = parent->fs;

	/* allocate cluster which will hold directory entries */
	cluster_t dir_cluster = fat_append_clusters(fs, 0, 1);
	if (!dir_cluster)
		return 0;

	/* clear cluster to prevent bogus directory entries */
	fat_clear_cluster(fs, dir_cluster);

	memset(dir_entry, 0, sizeof(*dir_entry));
	dir_entry->attributes = FAT_ATTRIB_DIR;

	/* create "." directory self reference */
	dir_entry->entry_offset = fs->header.cluster_zero_offset +
	    (offset_t) (dir_cluster - 2) * fs->header.cluster_size;
	dir_entry->long_name[0] = '.';
	dir_entry->cluster = dir_cluster;
	if (!fat_write_dir_entry(fs, dir_entry)) {
		fat_free_clusters(fs, dir_cluster);
		return 0;
	}

	/* create ".." parent directory reference */
	dir_entry->entry_offset += 32;
	dir_entry->long_name[1] = '.';
	dir_entry->cluster = parent->dir_entry.cluster;
	if (!fat_write_dir_entry(fs, dir_entry)) {
		fat_free_clusters(fs, dir_cluster);
		return 0;
	}

	/* fill directory entry */
	strncpy(dir_entry->long_name, dir,
		sizeof(dir_entry->long_name) - 1);
	dir_entry->cluster = dir_cluster;

	/* find place where to store directory entry */
	if (!
	    (dir_entry->entry_offset =
	     fat_find_offset_for_dir_entry(fs, parent, dir_entry))) {
		fat_free_clusters(fs, dir_cluster);
		return 0;
	}

	/* write directory to disk */
	if (!fat_write_dir_entry(fs, dir_entry)) {
		fat_free_clusters(fs, dir_cluster);
		return 0;
	}

	return 1;
}
#endif

/**
 * \ingroup fat_dir
 * Deletes a directory.
 *
 * This is just a synonym for fat_delete_file().
 * If a directory is deleted without first deleting its
 * subdirectories and files, disk space occupied by these
 * files will get wasted as there is no chance to release
 * it and mark it as free.
 * 
 * \param[in] fs The filesystem on which to operate.
 * \param[in] dir_entry The directory entry of the directory to delete.
 * \returns 0 on failure, 1 on success.
 * \see fat_create_dir
 */
#ifdef DOXYGEN
uint8 fat_delete_dir(struct fat_fs_struct * fs,
		     struct fat_dir_entry_struct * dir_entry);
#endif

/**
 * \ingroup fat_dir
 * Moves or renames a directory.
 *
 * This is just a synonym for fat_move_file().
 * 
 * \param[in] fs The filesystem on which to operate.
 * \param[in,out] dir_entry The directory entry of the directory to move.
 * \param[in] parent_new The handle of the new parent directory.
 * \param[in] dir_new The directory's new name.
 * \returns 0 on failure, 1 on success.
 * \see fat_create_dir, fat_delete_dir, fat_move_file
 */
#ifdef DOXYGEN
uint8 fat_move_dir(struct fat_fs_struct *fs,
		   struct fat_dir_entry_struct *dir_entry,
		   struct fat_dir_struct *parent_new, const char *dir_new);
#endif

#if DOXYGEN || FAT_DATETIME_SUPPORT
/**
 * \ingroup fat_file
 * Returns the modification date of a file.
 *
 * \param[in] dir_entry The directory entry of which to return the modification date.
 * \param[out] year The year the file was last modified.
 * \param[out] month The month the file was last modified.
 * \param[out] day The day the file was last modified.
 */
void fat_get_file_modification_date(const struct fat_dir_entry_struct
				    *dir_entry, uint16 * year,
				    uint8 * month, uint8 * day)
{
	if (!dir_entry)
		return;

	*year = 1980 + ((dir_entry->modification_date >> 9) & 0x7f);
	*month = (dir_entry->modification_date >> 5) & 0x0f;
	*day = (dir_entry->modification_date >> 0) & 0x1f;
}
#endif

#if DOXYGEN || FAT_DATETIME_SUPPORT
/**
 * \ingroup fat_file
 * Returns the modification time of a file.
 *
 * \param[in] dir_entry The directory entry of which to return the modification time.
 * \param[out] hour The hour the file was last modified.
 * \param[out] min The min the file was last modified.
 * \param[out] sec The sec the file was last modified.
 */
void fat_get_file_modification_time(const struct fat_dir_entry_struct
				    *dir_entry, uint8 * hour, uint8 * min,
				    uint8 * sec)
{
	if (!dir_entry)
		return;

	*hour = (dir_entry->modification_time >> 11) & 0x1f;
	*min = (dir_entry->modification_time >> 5) & 0x3f;
	*sec = ((dir_entry->modification_time >> 0) & 0x1f) * 2;
}
#endif

#if DOXYGEN || (FAT_WRITE_SUPPORT && FAT_DATETIME_SUPPORT)
/**
 * \ingroup fat_file
 * Sets the modification time of a date.
 *
 * \param[in] dir_entry The directory entry for which to set the modification date.
 * \param[in] year The year the file was last modified.
 * \param[in] month The month the file was last modified.
 * \param[in] day The day the file was last modified.
 */
void fat_set_file_modification_date(struct fat_dir_entry_struct *dir_entry,
				    uint16 year, uint8 month, uint8 day)
{
	if (!dir_entry)
		return;

	dir_entry->modification_date =
	    ((year - 1980) << 9) |
	    ((uint16) month << 5) | ((uint16) day << 0);
}
#endif

#if DOXYGEN || (FAT_WRITE_SUPPORT && FAT_DATETIME_SUPPORT)
/**
 * \ingroup fat_file
 * Sets the modification time of a file.
 *
 * \param[in] dir_entry The directory entry for which to set the modification time.
 * \param[in] hour The year the file was last modified.
 * \param[in] min The month the file was last modified.
 * \param[in] sec The day the file was last modified.
 */
void fat_set_file_modification_time(struct fat_dir_entry_struct *dir_entry,
				    uint8 hour, uint8 min, uint8 sec)
{
	if (!dir_entry)
		return;

	dir_entry->modification_time =
	    ((uint16) hour << 11) |
	    ((uint16) min << 5) | ((uint16) sec >> 1);
}
#endif

/**
 * \ingroup fat_fs
 * Returns the amount of total storage capacity of the filesystem in bytes.
 *
 * \param[in] fs The filesystem on which to operate.
 * \returns 0 on failure, the filesystem size in bytes otherwise.
 */
offset_t fat_get_fs_size(const struct fat_fs_struct * fs)
{
	if (!fs)
		return 0;

#if FAT_FAT32_SUPPORT
	if (fs->partition->type == PARTITION_TYPE_FAT32)
		return (offset_t) (fs->header.fat_size / 4 -
				   2) * fs->header.cluster_size;
	else
#endif
		return (offset_t) (fs->header.fat_size / 2 -
				   2) * fs->header.cluster_size;
}

/**
 * \ingroup fat_fs
 * Returns the amount of free storage capacity on the filesystem in bytes.
 *
 * \note As the FAT filesystem is cluster based, this function does not
 *       return continuous values but multiples of the cluster size.
 *
 * \param[in] fs The filesystem on which to operate.
 * \returns 0 on failure, the free filesystem space in bytes otherwise.
 */
offset_t fat_get_fs_free(const struct fat_fs_struct * fs)
{
	if (!fs)
		return 0;

	uint8 fat[32];
	struct fat_usage_count_callback_arg count_arg;
	count_arg.cluster_count = 0;
	count_arg.buffer_size = sizeof(fat);

	offset_t fat_offset = fs->header.fat_offset;
	uint32 fat_size = fs->header.fat_size;
	while (fat_size > 0) {
		uintptr_t length = UINTPTR_MAX - 1;
		if (fat_size < length)
			length = fat_size;

		if (!fs->partition->device_read_interval(fat_offset,
							 fat,
							 sizeof(fat),
							 length,
#if FAT_FAT32_SUPPORT
							 (fs->partition->
							  type ==
							  PARTITION_TYPE_FAT16)
							 ?
							 fat_get_fs_free_16_callback
							 :
							 fat_get_fs_free_32_callback,
#else
							 fat_get_fs_free_16_callback,
#endif
							 &count_arg)
		    )
			return 0;

		fat_offset += length;
		fat_size -= length;
	}

	return (offset_t) count_arg.cluster_count *
	    fs->header.cluster_size;
}

/**
 * \ingroup fat_fs
 * Callback function used for counting free clusters in a FAT.
 */
uint8 fat_get_fs_free_16_callback(uint8 * buffer, offset_t offset, void *p)
{
	struct fat_usage_count_callback_arg *count_arg =
	    (struct fat_usage_count_callback_arg *) p;
	uintptr_t buffer_size = count_arg->buffer_size;

	uintptr_t i;
	for (i = 0; i < buffer_size; i += 2, buffer += 2) {
		uint16 cluster = read16(buffer);
		if (cluster == HTOL16(FAT16_CLUSTER_FREE))
			++(count_arg->cluster_count);
	}

	return 1;
}

#if DOXYGEN || FAT_FAT32_SUPPORT
/**
 * \ingroup fat_fs
 * Callback function used for counting free clusters in a FAT32.
 */
uint8 fat_get_fs_free_32_callback(uint8 * buffer, offset_t offset, void *p)
{
	struct fat_usage_count_callback_arg *count_arg =
	    (struct fat_usage_count_callback_arg *) p;
	uintptr_t buffer_size = count_arg->buffer_size;

	uintptr_t i;
	for (i = 0; i < buffer_size; i += 4, buffer += 4) {
		uint32 cluster = read32(buffer);
		if (cluster == HTOL32(FAT32_CLUSTER_FREE))
			++(count_arg->cluster_count);
	}

	return 1;
}
#endif





// convenience functions placed here by Matt Olson
struct fat_file_struct *open_file_in_dir(struct fat_fs_struct *fs,
					 struct fat_dir_struct *dd,
					 const char *name)
{
	struct fat_dir_entry_struct file_entry;
	if (!find_file_in_dir(fs, dd, name, &file_entry))
		return 0;

	return fat_open_file(fs, &file_entry);
}


uint8 find_file_in_dir(struct fat_fs_struct * fs,
		       struct fat_dir_struct * dd, const char *name,
		       struct fat_dir_entry_struct * dir_entry)
{
	while (fat_read_dir(dd, dir_entry)) {
		if (strcmp(dir_entry->long_name, name) == 0) {
			fat_reset_dir(dd);
			return 1;
		}
	}

	return 0;
}
